import matplotlib.pyplot as plt
from matplotlib import rcParams

# 设置全局字体（例如 SimHei 或 Noto Sans CJK）
rcParams['font.sans-serif'] = ['SimHei']  # 黑体
rcParams['axes.unicode_minus'] = False    # 解决负号显示问题

import os, re, math, itertools
import numpy as np, pandas as pd, matplotlib.pyplot as plt
import networkx as nx
from scipy.spatial import cKDTree

# =====================
# 通用解析函数
# =====================
def _parse_coord_str(s):
    if not isinstance(s, str): return None
    m = re.search(r"\{([^}]*)\}", s)
    if not m: return None
    parts = m.group(1).split(",")
    if len(parts)<2: return None
    return (float(parts[0])/1000.0, float(parts[1])/1000.0)


def parse_segments_from_sheet(xls_path, sheet):
    df = pd.read_excel(xls_path, sheet_name=sheet)
    if sheet=="植物":
        rows=[]
        for _,row in df.iterrows():
            c=_parse_coord_str(row.iloc[0])
            v=row.iloc[1]
            try: r=float(v)/1000.0
            except:
                try: r=float(re.findall(r"[-+]?\d*\.\d+|\d+", str(v))[0])/1000.0
                except: r=np.nan
            if c and r==r: rows.append((c[0],c[1],r))
        return pd.DataFrame(rows, columns=["x","y","r"])
    segs,cur=[],[]
    for v in df.iloc[:,0].astype(str).tolist():
        if re.search(r"\{0;\s*\d+\}", v):
            if cur: segs.append(cur); cur=[]
            continue
        pt=_parse_coord_str(v)
        if pt: cur.append(pt)
    if cur: segs.append(cur)
    return segs

def sample_points(segments, step=2.0):
    pts=[]
    for seg in segments:
        pts.append(seg[0])
        for i in range(1,len(seg)):
            (x1,y1),(x2,y2)=seg[i-1],seg[i]
            dx,dy=x2-x1,y2-y1; dist=math.hypot(dx,dy)
            n=max(1,int(dist//step))
            for k in range(1,n+1): pts.append((x1+dx*k/n, y1+dy*k/n))
    return np.array(pts) if pts else np.zeros((0,2))

# =====================
# 趣味性建模
# =====================
def compute_fun(xls_path, out_dir, garden_name):
    os.makedirs(out_dir, exist_ok=True)
    roads     = parse_segments_from_sheet(xls_path,"道路")
    buildings = parse_segments_from_sheet(xls_path,"实体建筑")
    semi_open = parse_segments_from_sheet(xls_path,"半开放建筑")
    rockeries = parse_segments_from_sheet(xls_path,"假山")
    waters    = parse_segments_from_sheet(xls_path,"水体")
    plants    = parse_segments_from_sheet(xls_path,"植物")
    # 图构建
    def snap(p,grid=0.6): return (round(p[0]/grid)*grid, round(p[1]/grid)*grid)
    G=nx.Graph()
    def add_edge(p,q):
        if p==q: return
        d=math.hypot(q[0]-p[0], q[1]-p[1]); G.add_edge(p,q,length=d)
    for seg in roads:
        s=[snap(pt) for pt in seg]
        for i in range(1,len(s)): add_edge(s[i-1],s[i])
    G.remove_nodes_from([n for n in list(G.nodes()) if G.degree(n)==0])

    # 入口出口（最远叶子）
    endpoints=[n for n in G.nodes() if G.degree(n)==1]
    def farthest_pair(nodes):
        best,best_d=None,-1
        for s in nodes:
            lengths=nx.single_source_dijkstra_path_length(G,s,weight="length")
            for t,d in lengths.items():
                if t in nodes and d>best_d:
                    best_d=d; best=(s,t)
        return best
    (entry,exit_)=farthest_pair(endpoints)

    # 可视集
    elem_pts={
        "水体": sample_points(waters,2.0),
        "假山": sample_points(rockeries,2.0),
        "建筑": sample_points(buildings+semi_open,2.0),
        "植物": np.array(plants[["x","y"]]) if isinstance(plants,pd.DataFrame) else np.zeros((0,2)),
    }
    trees={k:(cKDTree(v) if len(v)>0 else None) for k,v in elem_pts.items()}
    def visible_set(pt,R=12.0):
        cats=set()
        for name,tree in trees.items():
            if tree and tree.query_ball_point(pt,r=R): cats.add(name)
        return cats
    def jaccard(a, b):
        return 0.0 if (not a and not b) else 1.0 - len(a & b) / len(a | b)
    def views_on_path(path,step=3.0):
        pts=[]
        for i in range(1,len(path)):
            (x1,y1),(x2,y2)=path[i-1],path[i]
            dx,dy=x2-x1,y2-y1; dist=math.hypot(dx,dy)
            n=max(1,int(dist//step))
            for k in range(n):
                t=k/n; pts.append((x1+dx*t,y1+dy*t))
        pts.append(path[-1]); return pts

    def interest_score(path):
        vpts=views_on_path(path)
        sets=[visible_set(p,12.0) for p in vpts]
        chg=[jaccard(sets[i-1],sets[i]) for i in range(1,len(sets))]
        mean_change=np.mean(chg) if chg else 0.0
        score=mean_change+0.1*len([n for n in path if G.degree(n)>=3])
        return score,mean_change,vpts,path

    # 遍历路径
    sp_len=nx.shortest_path_length(G,entry,exit_,weight="length")
    Lmax=1.8*sp_len

    def enum_paths(G,s,t,Lmax):
        stack=[(s,[s],0.0)]
        while stack:
            u,path,L=stack.pop()
            if u==t: yield path
            for v in G.neighbors(u):
                if v in path: continue
                L2=L+G[u][v]["length"]
                if L2<=Lmax: stack.append((v,path+[v],L2))
                
    best=None
    for path in itertools.islice(enum_paths(G,entry,exit_,Lmax),5000):
        s,mc,vpts,pp=interest_score(path)
        if not best or s>best[0]: best=(s,mc,vpts,pp)

    if not best: return None
    print(f"best{best}")
    s,mc,vpts,path=best
    print(f"s{s}")
    

# 导出结果
    df=pd.DataFrame({"x":[p[0] for p in vpts],"y":[p[1] for p in vpts]})
    df.to_csv(os.path.join(out_dir,f"{garden_name}_fun_route.csv"),index=False)
    plt.figure(); plt.plot([p[0] for p in path],[p[1] for p in path])
    plt.title(f"{garden_name} 趣味性最佳路线"); plt.axis('equal')
    plt.savefig(os.path.join(out_dir,f"{garden_name}_fun.png"),dpi=150); plt.close()
    return dict(name=garden_name,score=s,mean_change=mc)


# =====================
# 主程序（十园）
# =====================
file_map = {
    # "拙政园": r"data/拙政园数据坐标.xlsx",
    # "留园": r"data/留园数据坐标.xlsx",
    # "寄畅园": r"data/寄畅园数据坐标.xlsx",
    # "瞻园": r"data/瞻园数据坐标.xlsx",
    # "豫园": r"data/豫园数据坐标.xlsx",
    # "秋霞园": r"data/秋霞园数据坐标.xlsx",
    # "沈园": r"data/沈园数据坐标.xlsx",
    # "怡园": r"data/怡园数据坐标.xlsx",
    "耦园": r"data/耦园数据坐标.xlsx",
    # "绮园": r"data/绮园数据坐标.xlsx",
    
}

results=[]
for name,path in file_map.items():
    res=compute_fun(path,"./out_problem1",name)
    print(res)
    if res: results.append(res)
pd.DataFrame(results).to_csv("./out_problem1/fun_summary.csv",index=False)